/**
 * 
 */
package cn.bblocks.cacheTest.configuration;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.validation.Valid;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.cache.ScanRemoveRedisCacheWriterWithHotKey;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.jd.platform.hotkey.client.ClientStarter;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.util.Pool;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonAutoDetect;

/**
 * <p>Description: <／p>
 * <p>Company: sx<／p> 
 * @author jzhao
 *
 * @date 2019年5月5日
 */
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
	@Value("${spring.redis.host}")
	private String redisHost ;
	@Value("${spring.redis.port}")
	private Integer redisPort ;
	@Value("${spring.redis.password}")
	private String redisPassword ;
	
	@Value("${server.hotKeyEtcd:}")
	private String hotEtcd ;
	@Value("${server.name}")
	private String appName ;
	
	
	@Bean
	public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
	    	
	    	RedisCacheWriter cacheWriter = null;
	    	//如果存在 app.hotKeyEtcd ,则启用 热点key缓存方案
	    	if(Strings.isNullOrEmpty(hotEtcd) ) {
	    		cacheWriter = RedisCacheWriter.nonLockingScanClearRedisCacheWriter(redisConnectionFactory);
	    	}else {
	    		initJdHotKey();
	    		cacheWriter = new ScanRemoveRedisCacheWriterWithHotKey(redisConnectionFactory);
	    	}

	    	RedisCacheManager r=  new RedisCacheManager(
//	            RedisCacheWriter.nonLockingScanClearRedisCacheWriter(redisConnectionFactory),
//		            RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
	    			cacheWriter,
	            this.getRedisCacheConfigurationWithTtl(60*15), // 5分钟，后面改成900秒=15分钟
//	            this.getRedisCacheConfigurationWithTtl(5), // 测试用5秒
	            this.getRedisCacheConfigurationMap() // 指定 key 策略
	        );
	    	//((org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory)redisConnectionFactory).getPassword();;
	        return r;
	 }
	    
	    private void initJdHotKey() {
	    	ClientStarter.Builder builder = new ClientStarter.Builder();
	        ClientStarter starter = builder.setAppName(Strings.isNullOrEmpty(appName)?"app1":appName).setEtcdServer(hotEtcd).build();
	        starter.startPipeline();
	    }
	    
	    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
	        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
	        
	       
	        return redisCacheConfigurationMap;
	    }

	    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
	        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
	        ObjectMapper om = new ObjectMapper();
	        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
	        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
	        jackson2JsonRedisSerializer.setObjectMapper(om);

//	        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
//	        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
//	            RedisSerializationContext
//	                .SerializationPair
//	                .fromSerializer(jackson2JsonRedisSerializer)
//	        ).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
//	        		.entryTtl(Duration.ofSeconds(seconds));
	        
	        
	        if(Strings.isNullOrEmpty(redisPassword))
	        	redisPassword = null;
	        

	        Pool<Jedis> pool = new JedisPool(new GenericObjectPoolConfig(), redisHost, redisPort,
	        		Protocol.DEFAULT_TIMEOUT, redisPassword);;
	      
	        
	        
	        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
	                .entryTtl(Duration.ofSeconds(seconds)) // 60s缓存失效
//	                .prefixKeysWith("jzhao")
	                // 设置key的序列化方式
	                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
	                // 设置value的序列化方式
	                .serializeValuesWith(RedisSerializationContext
	    	                .SerializationPair
	    	                .fromSerializer(jackson2JsonRedisSerializer)
	    	                )
	                .poolWith(pool)
	                .defaultCleanPeriodWith(600)//10分钟清理一次
	               // .poolWith(50, "127.0.0.1", 6379, -1, null)
	                // 不缓存null值
	                //.disableCachingNullValues()
	               ;
	        
	        return config;
	    }
	 
	    // key键序列化方式
	    private RedisSerializer<String> keySerializer() {
	        return new StringRedisSerializer();
	    }
	@Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
 
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        redisSerializer.setObjectMapper(mapper);
        
        RedisSerializer stringSerializer = new StringRedisSerializer();

        template.setValueSerializer(stringSerializer);
 
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        
        template.afterPropertiesSet();
 
        return template;
    }

		/**
		 * redis key生成策略
		 * target: 类
		 * method: 方法
		 * params: 参数
		 * @return KeyGenerator
		 * 
		 * 注意: 该方法只是声明了key的生成策略,还未被使用,需在@Cacheable注解中指定keyGenerator
		 *      如: @Cacheable(value = "key", keyGenerator = "cacheKeyGenerator")
		 */
//	    @Bean
//	    public KeyGenerator cacheKeyGenerator() {
//	        return (target, method, params) -> {
//	            StringBuilder sb = new StringBuilder();
//	            sb.append(target.getClass().getName());
//	            sb.append(method.getName());
//	            for (Object obj : params) {
//	            	// 由于参数可能不同, hashCode肯定不一样, 缓存的key也需要不一样
//	                sb.append(FastJsonUtils.convertObjectToJSON(obj).hashCode());
//	            }
//	            return sb.toString();
//	        };
//	    }
//	    
//	 // 自定义key生成器
//	    @Bean
//	    public KeyGenerator keyGenerator(){
//	        return (o, method, params) ->{
//	            StringBuilder sb = new StringBuilder();
//	            sb.append(o.getClass().getName()); // 类目
//	            sb.append(method.getName()); // 方法名
//	            for(Object param: params){
//	                sb.append(param.toString()); // 参数名
//	            }
//	            return sb.toString();
//	        };
//	    }

}
